package edu.mju.annotation.servlet;

import edu.mju.annotation.annotation.XxAutowired;
import edu.mju.annotation.annotation.XxController;
import edu.mju.annotation.annotation.XxRequestMapping;
import edu.mju.annotation.annotation.XxService;
import edu.mju.annotation.handler.Handler;
import edu.mju.annotation.utils.LowerFirst;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author yiping_wang
 */
public class XxDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    /**
     * 存储：扫描的类的全限定类名
     */
    private List<String> classNames = new ArrayList<>();

    private Map<String, Object> ioc = new HashMap<>();

    /**
     * 存储url>method映射关系
     */
//    private Map<String, Object> handleMapping = new HashMap<>();
    private List<Handler> handleMapping = new ArrayList<>();

    @Override

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("----doGET----");
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("----doPOST----");

        //根据uri获取到能够处理当前请求的Handler（从handlerMapping中（list））
        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }
        //参数绑定
        //该方法所有参数得类型数组
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        //根据上述数组长度创建一个新的数组（参数数组，传入反射调用的）
        Object[] paramValues = new Object[parameterTypes.length];
        //以下就是为了向参数数组中设值，而且还得保证参数得顺序和方法中形参顺序一致。
        Map<String, String[]> parameterMap = req.getParameterMap();
        //遍历request中所有的参数 ，（填充除了request、response之外的参数）
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            //name=1&name=2 name[1,2]
            String value = StringUtils.join(Arrays.asList((entry.getValue())), ",");
            //如果参数和方法中的参数匹配上了，填充数据
            if (!handler.getParamIndexMapping().containsKey(entry.getKey())) {
                continue;
            }
            //方法形参确实有该参数，找到它得索引位置，对应得把参数值放入paramValues
            Integer index = handler.getParamIndexMapping().get(entry.getKey());
            //把前台传递过来的参数值，填充到对应得位置去
            paramValues[index] = value;
        }
        //获取HttpServletRequest参数位置
        Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        paramValues[requestIndex] = req;
        //获取HttpServletResponse参数位置
        Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        paramValues[responseIndex] = resp;
        //最终调用handler得method属性
        try {
            Object invoke = handler.getMethod().invoke(handler.getController(), paramValues);
            //简单操作，把方法返回的数据以字符串的形式写出
            resp.getWriter().write(invoke.toString());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }


    /**
     * 重写init方法
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //加载配置
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        loadConfig(contextConfigLocation);
        //注解扫描
        componentScan(properties.getProperty("scanPackage"));
        //初始化bean
        initBean();
        //依赖注入
        autowired();
        //映射url与method
        handleMapping();
        System.out.println("MVC 初始化完成！");
    }

    private Handler getHandler(HttpServletRequest request) {
        if (handleMapping.isEmpty()) {
            return null;
        }
        String requestURI = request.getRequestURI();
        for (Handler handler : handleMapping) {
            Matcher matcher = handler.getPattern().matcher(requestURI);
            if (!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return null;
    }

    private void handleMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> stringObjectEntry : ioc.entrySet()) {
            Class<?> aClass = stringObjectEntry.getValue().getClass();
            //排除不是controller
            if (!aClass.isAnnotationPresent(XxController.class)) {
                continue;
            }
            String url = "";
            if (aClass.isAnnotationPresent(XxRequestMapping.class)) {
                url = aClass.getAnnotation(XxRequestMapping.class).value();
            }
            //获取方法
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                //排除没有@XxRequestMapping注解的方法
                if (!method.isAnnotationPresent(XxRequestMapping.class)) {
                    continue;
                }
                String methodUrl = method.getAnnotation(XxRequestMapping.class).value();
                String result = url + methodUrl;

                //把method所有信息以及url封装为Handler
                Handler handler = new Handler(stringObjectEntry.getValue(), method, Pattern.compile(result));
                //获取方法所有参数
                Parameter[] parameters = method.getParameters();

                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    //只处理 HttpServletRequest req, HttpServletResponse resp参数
                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        //参数名称存HttpServletRequest||HttpServletResponse 顺序为i
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                    } else {
                        //直接存参数名称
                        handler.getParamIndexMapping().put(parameter.getName(), i);
                    }
                }
                handleMapping.add(handler);
            }

        }
    }

    private void autowired() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> stringObjectEntry : ioc.entrySet()) {
            //获取Bean对象中的字段信息
            Field[] declaredFields = stringObjectEntry.getValue().getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //如果不是自动注入
                if (!declaredField.isAnnotationPresent(XxAutowired.class)) {
                    continue;
                }
                XxAutowired annotation = declaredField.getAnnotation(XxAutowired.class);
                String beanName = annotation.value();
                if ("".equals(beanName.trim())) {
                    //没有配置具体的beanId，需要根据当前字段的类型注入（接口注入）  
                    beanName = declaredField.getType().getName();
                }
                //开启赋值
                declaredField.setAccessible(true);
                try {
                    declaredField.set(stringObjectEntry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void initBean() {
        if (classNames.size() == 0) {
            return;
        }
        try {
            for (String className : classNames) {
                //反射得到对象
                Class<?> aClass = Class.forName(className);
                //归类
                if (aClass.isAnnotationPresent(XxController.class)) {
                    //controllerBean
                    String simpleName = LowerFirst.lowerFirst(aClass.getSimpleName());
                    Object o = aClass.newInstance();
                    ioc.put(simpleName, o);
                } else if (aClass.isAnnotationPresent(XxService.class)) {
                    XxService annotation = aClass.getAnnotation(XxService.class);
                    //获取注解值
                    String value = annotation.value();
                    if (!"".equals(value.trim())) {
                        //如果指定id
                        ioc.put(value, aClass.newInstance());
                    } else {
                        //未指定
                        ioc.put(LowerFirst.lowerFirst(aClass.getSimpleName()), aClass.newInstance());
                    }

                    //service一般有接口
                    Class<?>[] serviceInterface = aClass.getInterfaces();
                    for (Class<?> aClass1 : serviceInterface) {
                        //接口类名作为id
                        ioc.put(aClass1.getName(), aClass.newInstance());
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 扫描类 并把所有扫描的类的全类名放到list中
     */
    private void componentScan(String scanPackage) {
        //1.获得classPath路径
        String classPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        //2.拼接,得到scanPackage在磁盘上的路径
        String scanPackagePath = classPath + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            //如果是文件夹 向下继续
            if (file.isDirectory()) {
                componentScan(scanPackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    /**
     * 加载配置
     *
     * @param contextConfigLocation web.xml中的配置信息
     */
    private void loadConfig(String contextConfigLocation) {
        //加载指定路径为流
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
